﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Newtonsoft.Json;
using System.IO;
using System.Windows.Forms;
using System.Net;
using System.ComponentModel;
using System.Diagnostics;

namespace MSCOCO_Downloader
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private COCODataset dataset = null;
        private int alreadyDownloaded = 0;
        private long filesSize = 0;
        private BackgroundWorker worker;
        private string downloadDirectory = "";
        private System.Threading.ManualResetEvent _busy = new System.Threading.ManualResetEvent(false);
        private bool isClosing = false;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void jsonOpenButton_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "JSON files|*.json";
            if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
            jsonFileTextBox.Text = ofd.FileName;
            try {
                StreamReader file = File.OpenText(ofd.FileName);

                JsonSerializer serializer = new JsonSerializer();
                dataset = (COCODataset)serializer.Deserialize(file, typeof(COCODataset));
                if (dataset.images == null || dataset.images.Count == 0)
                {
                    System.Windows.MessageBox.Show("No images data in file");
                    dataset = null;
                }
                else
                {
                    checkAlreadyDownloaded();
                    writeStatus();
                }
            } catch(Exception ex)
            {
                System.Windows.MessageBox.Show("Failed to load file: " + ex.Message);
                dataset = null;
            }
        }

        private void folderBrowseButton_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            fbd.SelectedPath = downloadFolderTextBox.Text;
            if(fbd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
            downloadDirectory = downloadFolderTextBox.Text = fbd.SelectedPath + "\\";
            checkAlreadyDownloaded();
            writeStatus();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            downloadFolderTextBox.Text = downloadDirectory = KnownFolders.GetPath(KnownFolder.Downloads) + "\\COCO Dataset\\";
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
            worker.DoWork += Worker_DoWork;
            worker.ProgressChanged += Worker_ProgressChanged;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            writeStatus();
            progressStatusTextBlock.Text = "Ready...";
        }

        private void checkAlreadyDownloaded()
        {
            alreadyDownloaded = 0;
            filesSize = 0;
            if (dataset == null)
            {
                
            }
            else
            {
                int finished = 0;
                foreach (COCOImage image in dataset.images)
                {
                    FileInfo finfo = new FileInfo(downloadDirectory + image.file_name);
                    bool downloaded = finfo.Exists && finfo.Length > 0;
                    image.downloaded = downloaded;
                    if (downloaded) { alreadyDownloaded++; filesSize += finfo.Length; }
                    finished++;
                }
                progressBar.Value = 0;
                progressStatusTextBlock.Text = "Loaded annotation file..."; 
            }
        }

        private void writeStatus()
        {
            statusTextBlock.Text = "Initial State:"
                + "\nTotal Number of Images = " + (dataset==null?0:dataset.images.Count)
                + "\nAlready Downloaded Before = " + alreadyDownloaded
                + "\nSize of Already Downloaded Images = " + fileSizeFormatter(filesSize)
                + "\nRemaining Images = " + ((dataset == null ? 0 : dataset.images.Count) - alreadyDownloaded);
            if (dataset == null)
                progressBar.Value = 0;
            else
                progressBar.Value = alreadyDownloaded * 100 / dataset.images.Count;
        }

        private void DownloadButton_Click(object sender, RoutedEventArgs e)
        {
            if(dataset == null) { System.Windows.MessageBox.Show("Please Select Annotations Files"); return; }
            if(!worker.IsBusy) worker.RunWorkerAsync();
            _busy.Set();
            DownloadButton.IsEnabled = false;
            PauseButton.IsEnabled = true;
            CancelButton.IsEnabled = true;
            
        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (isClosing) this.Close();
            checkAlreadyDownloaded();
            writeStatus();
            DownloadButton.IsEnabled = true;
            PauseButton.IsEnabled = false;
            CancelButton.IsEnabled = false;
            progressBar.Value = 100;
            progressStatusTextBlock.Text = "Finished...";
        }

        private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
            progressStatusTextBlock.Text = (string)e.UserState + " | " + e.ProgressPercentage + "%";
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {

            if (!Directory.Exists(downloadDirectory)) Directory.CreateDirectory(downloadDirectory);

            Stopwatch stopwatch = new Stopwatch();
            

            int finished = 0, toBeDownloaded = dataset.images.Count - alreadyDownloaded; long downloadedsize = 0, failed = 0;
            worker.ReportProgress((finished + alreadyDownloaded) * 100 / dataset.images.Count, "Finished in Current Session = " + finished + " | Overall = " + (finished + alreadyDownloaded) + " | Remaining = " + (dataset.images.Count - alreadyDownloaded - finished) + " | Failed = " + failed + "\nDownloaded Size in Current Session  = " + fileSizeFormatter(downloadedsize) + "\nOverall Downloaded Size = " + fileSizeFormatter(downloadedsize + filesSize));
            int finishedPerInterval = 0;
            long downloadedPerInterval = 0;
            TimeSpan remaining = TimeSpan.Zero;
            long speed = 0;

            using (WebClient webClient = new WebClient())
            {
                stopwatch.Start();

                while (finished + alreadyDownloaded < dataset.images.Count)
                {
                    if (worker.CancellationPending) break;

                    foreach (COCOImage image in dataset.images)
                    {
                        _busy.WaitOne();

                        if (worker.CancellationPending) break;

                        if (image.downloaded) continue;
                        try
                        {
                            webClient.DownloadFile(image.coco_url, downloadDirectory + image.file_name);
                        }
                        catch (Exception ex)
                        {
                            try
                            {
                                webClient.DownloadFile(image.flickr_url, downloadDirectory + image.file_name);
                            }
                            catch (Exception ex2)
                            {
                                failed++;
                            }
                        }
                        FileInfo finfo = new FileInfo(downloadDirectory + image.file_name);
                        if (finfo.Exists && finfo.Length > 0)
                        {
                            downloadedsize += finfo.Length;
                            downloadedPerInterval += finfo.Length;
                            image.downloaded = true;
                            finished++;
                            finishedPerInterval++;
                        }
                        if (stopwatch.Elapsed.Seconds >= 1)
                        {
                            speed = 1000 * downloadedPerInterval / stopwatch.ElapsedMilliseconds;
                            remaining = new TimeSpan(0, 0, 0, (int)((dataset.images.Count - alreadyDownloaded - finished) * stopwatch.ElapsedMilliseconds / finishedPerInterval));
                            finishedPerInterval = 0;
                            downloadedPerInterval = 0;
                            stopwatch.Restart();

                        }
                        worker.ReportProgress((finished + alreadyDownloaded) * 100 / dataset.images.Count, "Finished in Current Session = " + finished + " | Overall = " + (finished + alreadyDownloaded) + " | Remaining = " + (dataset.images.Count - alreadyDownloaded - finished) + " | Failed = " + failed + "\nDownloaded Size in Current Session  = " + fileSizeFormatter(downloadedsize) + "\nOverall Downloaded Size = " + fileSizeFormatter(downloadedsize + filesSize) + "\nSpeed = " + fileSizeFormatter(speed) + "/sec | Estimated Time Remaining = " + remaining.ToString());

                    }

                }

                stopwatch.Stop();
            }

        }

        private void PauseButton_Click(object sender, RoutedEventArgs e)
        {
            _busy.Reset();
            PauseButton.IsEnabled = false;
            DownloadButton.IsEnabled = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (worker.IsBusy) worker.CancelAsync();
            _busy.Set();
            PauseButton.IsEnabled = false;
            DownloadButton.IsEnabled = false;
            CancelButton.IsEnabled = false;
        }

        private string fileSizeFormatter(long Size)
        {
            if (Size == 0) return "0 B";
            string output = "";
            string[] arr = { "B", "KB", "MB", "GB", "TB" };
            int i = 0;
            while(Size != 0)
            {
                if(i == arr.Count() - 1)
                {
                    output = Size + " " + arr[i] + " " + output;
                    Size = 0;
                }
                else
                {
                    output = (Size%1024) + " " + arr[i] + " " + output;
                    Size /= 1024;
                }
                i++;
            }
            return output;
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (worker.IsBusy)
            {
                e.Cancel = true;
                worker.CancelAsync();
                _busy.Set();
                isClosing = true;
            }
        }
    }

}
